#include "IDocumentObject.h"

#include "Document.h"
#include "IDocumentObjectListener.h"

#include <QEvent>
#include <QDebug>
#include <QDynamicPropertyChangeEvent>
#include <QMetaObject>
#include <QMetaProperty>

namespace LDO
{

    class IDocumentObjectPrivate
    {
        Q_DISABLE_COPY(IDocumentObjectPrivate)
        Q_DECLARE_PUBLIC(IDocumentObject)
        IDocumentObject * const q_ptr;

        explicit IDocumentObjectPrivate(IDocumentObject *object):
            q_ptr(object)
        {}

        const QUuid uuid = QUuid::createUuid();
        QString userName;
        QSet<IDocumentObjectListener*> listeners;
        QMap<int, QMetaProperty> signalIndexToMetaProperty;
    };
}

using namespace LDO;

IDocumentObject::IDocumentObject(Document *document)
    : QObject(document)
    , d_ptr(new IDocumentObjectPrivate(this))
{
    static int counter = 0;
    setObjectName(QString("NoName%1").arg(counter++));
}

IDocumentObject::~IDocumentObject()
{
    Q_D(IDocumentObject);

    if (!d->listeners.isEmpty())
    {
        qWarning() << "IDocumentObject::~IDocumentObject: listener list is not empty";
        return;
    }

}

bool IDocumentObject::hasEditor() const
{
    return false;
}

int IDocumentObject::childObjectCount() const
{
    return children().count();
}

int IDocumentObject::parentObjectIndex() const
{
    if (parentObject() == nullptr)
    {
        qWarning() << "IDocumentObject::parentObjectIndex: No parent";
        return -1;
    }
    return parentObject()->children().indexOf(const_cast<IDocumentObject*>(this));
}

IDocumentObject *IDocumentObject::childObject(int index) const
{
    if (index < 0 || index >= children().count())
    {
        qWarning() << "IDocumentObject::childObject: Bad index";
        return nullptr;
    }
    return qobject_cast<IDocumentObject*>(children().at(index));
}

IDocumentObject *IDocumentObject::parentObject() const
{
    return qobject_cast<IDocumentObject*>(QObject::parent());
}

void IDocumentObject::addChildObject(IDocumentObject *child)
{
    if (child == nullptr)
    {
        qWarning() << "IDocumentObject::addChildObject: Attempting to add a nullptr";
        return;
    }

    if (children().contains(child))
    {
        qWarning() << "IDocumentObject::addChildObject: object is already a child" << child;
        return;
    }
    int index = childObjectCount();
    beginAddChild(child, index);
    child->setParent(this);
    endAddChild(child, index);
}

void IDocumentObject::removeChildObject(IDocumentObject *child)
{
    if (child == nullptr)
    {
        qWarning() << "IDocumentObject::removeChildObject: Attempting to remove a nullptr";
        return;
    }

    if (!children().contains(child))
    {
        qWarning() << "IDocumentObject::removeChildObject: No such child" << child;
        return;
    }

    int index = children().indexOf(child);
    beginRemoveChild(child, index);
    child->setParent(nullptr);
    endRemoveChild(child, index);
}

void IDocumentObject::registerListener(IDocumentObjectListener *listener)
{
    Q_D(IDocumentObject);

    if (listener == nullptr)
    {
        qWarning() << "IDocumentObject::registerListener: Attempting to register a nullptr";
        return;
    }
    if (d->listeners.contains(listener))
    {
        qWarning() << "IDocumentObject::registerListener: Attempting to register a listener twice";
        return;
    }

    d->listeners.insert(listener);
}

void IDocumentObject::unregisterListener(IDocumentObjectListener *listener)
{
    Q_D(IDocumentObject);

    if (listener == nullptr)
    {
        qWarning() << "IDocumentObject::unregisterListener: Attempting to unregister a nullptr";
        return;
    }
    if (!d->listeners.contains(listener))
    {
        qWarning() << "IDocumentObject::unregisterListener: Attempting to unregister an unknown listener";
        return;
    }
    d->listeners.remove(listener);
}

QUuid IDocumentObject::objectId() const
{
    Q_D(const IDocumentObject);
    return d->uuid;
}

QString IDocumentObject::objectUserName() const
{
    Q_D(const IDocumentObject);
    if (d->userName.isEmpty())
        return objectName();
    return d->userName;
}

void IDocumentObject::setObjectUserName(const QString &name)
{
    Q_D(IDocumentObject);
    if (d->userName == name)
        return;

    _IDO_SET_PROPERTY3(objectUserName, name, d->userName);
}

void IDocumentObject::beginAddChild(IDocumentObject *child, int index)
{
    Q_D(IDocumentObject);
    for (auto listener: d->listeners)
        listener->documentObjectAboutToBeInserted(this, child, index);
}

void IDocumentObject::endAddChild(IDocumentObject *child, int index)
{
    Q_D(IDocumentObject);
    for (auto listener: d->listeners)
        listener->documentObjectInserted(this, child, index);
}

void IDocumentObject::beginRemoveChild(IDocumentObject *child, int index)
{
    Q_D(IDocumentObject);
    for (auto listener: d->listeners)
        listener->documentObjectAboutToBeRemoved(this, child, index);
}

void IDocumentObject::endRemoveChild(IDocumentObject *child, int index)
{
    Q_D(IDocumentObject);
    for (auto listener: d->listeners)
        listener->documentObjectRemoved(this, child, index);
}

void IDocumentObject::beginSetProperty(const char *name)
{
    Q_D(IDocumentObject);
    for (int i=0; i<metaObject()->propertyCount(); i++)
    {
        QMetaProperty metaProperty = metaObject()->property(i);
        if (qstrcmp(metaProperty.name(), name) != 0)
            continue;
        QVariant value = metaProperty.read(this);
        for (auto listener: d->listeners)
            listener->documentObjectAboutToChangeProperty(this, name, value);
        return;
    }
    // FIXME: dynamic properties
    qWarning() << "IDocumentObject::beginSetProperty: Unknown property" << name;
}

void IDocumentObject::endSetProperty(const char *name)
{
    Q_D(IDocumentObject);
    for (int i=0; i<metaObject()->propertyCount(); i++)
    {
        QMetaProperty metaProperty = metaObject()->property(i);
        if (qstrcmp(metaProperty.name(), name) != 0)
            continue;
        QVariant value = metaProperty.read(this);
        for (auto listener: d->listeners)
            listener->documentObjectPropertyChanged(this, name, value);
        if (metaProperty.hasNotifySignal())
        {
            QByteArray typeName = value.typeName();
            QGenericArgument argument(typeName,
                                      const_cast<void*>(value.constData()));
            metaProperty.notifySignal().invoke(this, argument);
        }
        return;
    }
    // FIXME: dynamic properties
    qWarning() << "IDocumentObject::endSetProperty: Unknown property" << name;
}

// TODO: Dynamic properties
bool IDocumentObject::event(QEvent *event)
{
    //    if (event->type() == QEvent::DynamicPropertyChange)
    //    {
    //        auto propertyEvent = static_cast<QDynamicPropertyChangeEvent *>(event);
    //    }
    return QObject::event(event);
}

